#include "header.h"
#include <iostream>
#include <new>
#include <cstring>
#include <cassert>

char * const FIFO="FIFO";
char * const LIFO="LIFO";

Queue::Queue() {
	data = NULL;
}

entity* Queue::index2Pointer(unsigned i) {
	return ((data->queueHead) + i) -  (((data->queueHead) + i >= (data->buffer) + (data->bufferSize)) ? (data->bufferSize) : 0);
}

entity Queue::getEntity(unsigned i) {
	if (data == NULL) {
		throw LOGICERROR_NULLQUEUE;
	}
	if ( i >= data->count ) {
		throw LOGICERROR_ENTITYNOTEXIST;
	}
	return *index2Pointer(i);
}

unsigned Queue::getCount() {
	if (data == NULL) {
		throw LOGICERROR_NULLQUEUE;
	}
	return data->count;
}

unsigned Queue::getSize() {
	if (data == NULL) {
		throw LOGICERROR_NULLQUEUE;
	}
	return data->size;
}

unsigned Queue::getId() {
	if (data == NULL) {
		throw LOGICERROR_NULLQUEUE;
	}
	return data->id;
}

void Queue::eventHandler(double time) {
	if (time < (data->st).timeLength) {
		throw LOGICERROR_TIME;
	}
	(data->st).numInQueueIntegral += (data->count) * ( time - (data->st).timeLength );
	(data->st).timeLength = time;
}
void Queue::term(double time) {
	if (data == NULL) {
		throw LOGICERROR_NULLQUEUE;
	}
	eventHandler(time);
}
void Queue::enqueue(entity e, double time) {
	if (data == NULL) {
		throw LOGICERROR_NULLQUEUE;
	}
	e.enqueue() = time;
	e.queueId() = getId();
	eventHandler(time);
	if (data->count == data->bufferSize) {
		if ( data->size != 0) {		//if not inf queue, balk
			e.balk()=true;
			(data->st).balk++;
			if (*(data->report)) {
				std::cout << "Balk   : Queue:   " << (data->id) << "\tentity: " << e.getId() << "\ttime = " << time << std::endl;
			}
			return;
		}
		else {				//if inf queue, enlarge buffer
			entity *old;
			int qp1,qp2;
			old = data->buffer;
			try {
				data->buffer = new entity[2 * (data->bufferSize)];
			}
			catch (std::bad_alloc &e) {
				throw MEMERROR_ALLOC;
			}
			qp1 = data->bufferSize - (data->queueHead - old);
			qp2 = data->count - qp1;
			assert(qp1+qp2 == data->bufferSize && data->bufferSize == data->count && qp1 >=0 && qp2 >=0);
			memcpy(data->buffer, data->queueHead, qp1 * sizeof(entity));
			memcpy(data->buffer + qp1, old, qp2 * sizeof(entity));

			data->queueHead = data->buffer;
			data->bufferSize = 2 * data->bufferSize;
			
			try {
				delete []old;
			}
			catch (...) {
				throw MEMERROR_FREE;
			}
		}
	}
	*index2Pointer(data->count) = e;
	(data->count)++;
	if (*(data->report)) {
		std::cout << "Enqueue: Queue:   " << (data->id) << "\tentity: " << e.getId() << "\ttime = " << time << std::endl;
	}
}

entity Queue::dequeue(double time) {
	if (data == NULL) {
		throw LOGICERROR_NULLQUEUE;
	}
	eventHandler(time);
	if (data->count == 0) {
		throw LOGICERROR_EMPTYQUEUE;
		return *(data->queueHead);	//dummy return
	}
	entity *ptr;
	ptr = this->dequeue_by_policy();
	if (*(data->report)) {
		std::cout << "Dequeue: Queue:   " << (data->id) << "\tentity: " << ptr->getId() << "\ttime = " << time << std::endl;
	}
	ptr->enserver() = time;
	(data->st).dequeueCounter++;
	(data->st).totalWaitingTime += (ptr->enserver()) - (ptr->enqueue());
	return *ptr;
}

QueueMgr::QueueMgr() {
	count=0;
	head=NULL;
	report=false;
}

QueueMgr::~QueueMgr() {
	Qnode *ptr;
	ptr=head;
	if (true) {
		std::cout << "=============Queue  Statistics===========" << std::endl;
	}
	while (head !=NULL) {
		ptr = head;
		head = head->next;
		if (true) {
			float avgNumInQueue;
			float avgWaitingTime;
			try{
				avgNumInQueue = (ptr->st).numInQueueIntegral/(ptr->st).timeLength;
				avgWaitingTime = (ptr->st).totalWaitingTime/(ptr->st).dequeueCounter;
			}
			catch (...) {
				throw MATHERROR_ST;
			}
			std::cout << "Queue " << (ptr->id) << ':' << std::endl;
			std::cout << "\tQueue.Policy     = " << (ptr->type) << std::endl;
			std::cout << "\tQueue.Capacity   = ";
			if (ptr->size == 0) {
				std::cout << "Inf" << std::endl;
			}
			else {
				std::cout << (ptr->size) << std::endl;
			}
			std::cout << "\tQueue.Balk.Num   = " << ((ptr->st).balk) << std::endl;
			std::cout << "\tEnqueue.Counter  = " << ((ptr->st).dequeueCounter + ptr->count) << std::endl;
			std::cout << "\tDequeue.Counter  = " << ((ptr->st).dequeueCounter) << std::endl;
			std::cout << "\tavg.Num.In.Queue = " << avgNumInQueue << std::endl;
			std::cout << "\tavg.Waiting.Time = " << avgWaitingTime << std::endl;
		}
		try {
			delete [](ptr->buffer);
			delete ptr;
		}
		catch (...) {
			throw MEMERROR_FREE;
		}
	}
}

unsigned QueueMgr::getCount() {
	return count;
}

Queue QueueMgr::newQueue(char * type, unsigned buffer_size) {
	Qnode *ptr;
	try {
		ptr = new Qnode;
	}
	catch (std::bad_alloc &e) {
		throw MEMERROR_ALLOC;
	}
	if ( ptr == NULL ) {
		throw MEMERROR_ALLOC;
	}
	ptr->id = count;
	ptr->size = buffer_size;
	if ( buffer_size == 0 ) {	//capacity = inf
		ptr->bufferSize=32;
	}
	else {
		ptr->bufferSize=buffer_size;
	}
	ptr->count = 0;
	(ptr->st).balk = 0;
	ptr->report=&report;
	(ptr->st).dequeueCounter = 0;
	(ptr->st).timeLength = 0;
	(ptr->st).totalWaitingTime = 0;
	(ptr->st).numInQueueIntegral = 0;
	try {
		ptr->buffer = (entity *) new entity[ptr->bufferSize];
	}
	catch (std::bad_alloc &e) {
		throw MEMERROR_ALLOC;
	}
	if (ptr->buffer == NULL) {
		throw MEMERROR_ALLOC;
	}
	ptr->queueHead = ptr->buffer;

	if (strcmp(type,FIFO) == 0) {
		ptr->type = FIFO;
	}
	else if (strcmp(type,LIFO) == 0) {
		ptr->type = LIFO;
	}
	else {
		throw LOGICERROR_QUEUETYPEUNKNOWN;
	}
	count++;
	ptr->next = head;
	head = ptr;
	Queue r;
	r.data=ptr;
	return r;
}

double QueueMgr::avgWaitingTime() {
	Qnode *ptr;
	unsigned i = 0;
	double j = 0;
	for (ptr = head; ptr != NULL; ptr = ptr -> next) {
		i += (ptr->st).dequeueCounter;
		j += (ptr->st).totalWaitingTime;
	}
	return j/i;
}

entity* Queue::dequeue_by_policy() {
	if (strcmp(data->type, FIFO) == 0) {
		entity *ptr;
		ptr=data->queueHead;
		data->queueHead=index2Pointer(1);
		(data->count)--;
		return ptr;
	}
	else if (strcmp(data->type, LIFO) == 0) {
		(data->count)--;
		return index2Pointer(data->count);
	}
	else {
		throw LOGICERROR_QUEUETYPEUNKNOWN;
		return data->queueHead;
	}
}

Queue QueueMgr::getQueue(unsigned i) {
	Qnode *ptr=NULL;
	unsigned j;

	if (i >= count ) {
		throw LOGICERROR_INVAILDID;
	}
	ptr=head;

	if (ptr == NULL) {
		throw LOGICERROR_LINKTAB;
	}
	for (j=count; j != i+1; j--) {
		ptr=ptr->next;
		if (ptr == NULL) {
			throw LOGICERROR_LINKTAB;
		}
	}
	Queue r;
	r.data=ptr;
	return r; 
}
void QueueMgr::term(double time) {
	Qnode *ptr;
	Queue q;
	ptr=head;
	while ( ptr!=NULL ) {
		q.data=ptr;
		q.term(time);
		ptr = ptr->next;
	}
}
